#![allow(dead_code, unused)]

use std::{
    ffi::*, fs, mem::transmute, ptr::{copy, copy_nonoverlapping, null, null_mut}, sync::{Arc, Mutex}
};
use ash::{
    ext::{debug_utils, device_fault}, vk::{self, Extent2D, Handle}, Device, Entry, Instance
};
use getset::{CloneGetters, CopyGetters, Getters, MutGetters, Setters, WithSetters};
use crate::engine::{njm_device::NjmDevice, njm_model::Vertex};

#[derive(Getters, Setters, WithSetters, MutGetters, CopyGetters, CloneGetters, Default)]
pub struct PipelineConfigInfo<'a> {
    
    #[getset(get = "pub")]
    pub binding_descriptions: Vec<vk::VertexInputBindingDescription>,

    #[getset(get = "pub")]
    pub attribute_descriptions: Vec<vk::VertexInputAttributeDescription>,

    #[getset(get = "pub")]
    pub viewport_info: vk::PipelineViewportStateCreateInfo<'a>,

    #[getset(get = "pub")]
    pub input_assembly_info: vk::PipelineInputAssemblyStateCreateInfo<'a>,

    #[getset(get = "pub")]
    pub ras_info: vk::PipelineRasterizationStateCreateInfo<'a>,

    #[getset(get = "pub")]
    pub multi_sample_info: vk::PipelineMultisampleStateCreateInfo<'a>,

    #[getset(get = "pub")]
    pub color_blend_attachment: vk::PipelineColorBlendAttachmentState,

    #[getset(get = "pub")]
    pub color_blend_info: vk::PipelineColorBlendStateCreateInfo<'a>,

    #[getset(get = "pub")]
    pub depth_stencil_info: vk::PipelineDepthStencilStateCreateInfo<'a>,

    #[getset(get = "pub")]
    pub dynamic_state_enables: Vec<vk::DynamicState>,

    #[getset(get = "pub")]
    pub dynamic_state_info: vk::PipelineDynamicStateCreateInfo<'a>,

    // 
    #[getset(get = "pub")]
    pub pipeline_layout: vk::PipelineLayout,

    #[getset(get = "pub")]
    pub render_pass: vk::RenderPass,

    #[getset(get = "pub")]
    pub subpass: u32,
}

#[derive(Getters, Setters, WithSetters, MutGetters, CopyGetters, CloneGetters)]
pub struct NjmPipeline {

    dev: Arc<Mutex<NjmDevice>>,

    #[getset(get = "pub")]
    graphics_pipeline: vk::Pipeline,

    #[getset(get = "pub")]
    vshader: vk::ShaderModule,

    #[getset(get = "pub")]
    fshader: vk::ShaderModule,

}

impl NjmPipeline {
    pub fn new(
        dev: Arc<Mutex<NjmDevice>>, 
        vpath: &str,
        fpath: &str,
        config: &PipelineConfigInfo,
    ) -> Self {

        assert!(!config.pipeline_layout.is_null(), "[NjmPipeline::createGraphicsPipeline] : PipelineConfigInfo has no layout");
        assert!(!config.render_pass.is_null(), "[NjmPipeline::createGraphicsPipeline] : PipelineConfigInfo has no renderpass");
        let vcode = fs::read(vpath).unwrap();
        let fcode = fs::read(fpath).unwrap();
        let vshader = Self::create_shader_module(dev.clone(), vcode);
        let fshader = Self::create_shader_module(dev.clone(), fcode);
        let pipeline = Self::create_pipeline(dev.clone(), &vshader, &fshader, config);

        return Self {
            dev: dev,
            vshader: vshader,
            fshader: fshader,
            graphics_pipeline: pipeline,
        };
    }

    pub fn create_pipeline(
        dev: Arc<Mutex<NjmDevice>>, 
        vshader: &vk::ShaderModule,
        fshader: &vk::ShaderModule,
        config: &PipelineConfigInfo,
    ) -> vk::Pipeline {
        let v_entry_name = CString::new("main").unwrap();
        let f_entry_name = CString::new("main").unwrap();
        let vstage = vk::PipelineShaderStageCreateInfo::default()
            .stage(vk::ShaderStageFlags::VERTEX)
            .module(*vshader)
            .name(&v_entry_name);
        let fstage = vk::PipelineShaderStageCreateInfo::default()
            .stage(vk::ShaderStageFlags::FRAGMENT)
            .module(*fshader)
            .name(&f_entry_name);
        let stages = [vstage, fstage];

        let vert_input_state_info = vk::PipelineVertexInputStateCreateInfo::default()
            .vertex_binding_descriptions(&config.binding_descriptions)
            .vertex_attribute_descriptions(&config.attribute_descriptions);

        let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
            .stages(&stages)
            .vertex_input_state(&vert_input_state_info)
            .input_assembly_state(&config.input_assembly_info)
            .viewport_state(&config.viewport_info)
            .rasterization_state(&config.ras_info)
            .multisample_state(&config.multi_sample_info)
            .color_blend_state(&config.color_blend_info)
            .depth_stencil_state(&config.depth_stencil_info)
            .dynamic_state(&config.dynamic_state_info)
            .layout(config.pipeline_layout)
            .render_pass(config.render_pass)
            .subpass(config.subpass)
            .base_pipeline_index(-1)
            .base_pipeline_handle(vk::Pipeline::null());
            
        let njm_dev = dev.lock().unwrap();
        let pipelines = unsafe { njm_dev.device().create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None).unwrap() };
        return pipelines[0];
    }

    pub fn create_shader_module(dev: Arc<Mutex<NjmDevice>>, raw_code: Vec<u8>) -> vk::ShaderModule {
        if raw_code.len() % 4 != 0 {
            panic!("shader's spirv code do not complete!");
        }
        let len = raw_code.len() / 4;
        let cap = raw_code.capacity() / 4;
        let code = unsafe {
            std::slice::from_raw_parts(
                raw_code.as_ptr() as *const u32,
                raw_code.len() / 4
            )
        };
        // let p_spirv_code: *mut u32 = unsafe { transmute(raw_code.as_mut_ptr()) };
        // let spirv_code: Vec<u32> = unsafe { Vec::from_raw_parts(p_spirv_code, len, cap) };

        let shader_info = vk::ShaderModuleCreateInfo::default().code(code);
        let njm_dev = dev.lock().unwrap();
        let shader_module = unsafe { njm_dev.device().create_shader_module(&shader_info, None).unwrap() };
        return shader_module;
    }

    pub fn bind(&self, cmdbuf: vk::CommandBuffer) {
        let njm_dev = self.dev.lock().unwrap();
        unsafe { njm_dev.device().cmd_bind_pipeline(cmdbuf, vk::PipelineBindPoint::GRAPHICS, self.graphics_pipeline) };
    }

    pub fn default_pipeline_config(pipeline_config: &mut PipelineConfigInfo) {
        pipeline_config.input_assembly_info
            .topology(vk::PrimitiveTopology::TRIANGLE_LIST)
            .primitive_restart_enable(false);

        pipeline_config.viewport_info
            .viewport_count(1)
            .viewports(&[])
            .scissor_count(1)
            .scissors(&[]);

        pipeline_config.ras_info
            .cull_mode(vk::CullModeFlags::NONE)
            .front_face(vk::FrontFace::CLOCKWISE)
            .line_width(1.0)
            .polygon_mode(vk::PolygonMode::FILL)
            .rasterizer_discard_enable(false)
            .depth_clamp_enable(false)
            .depth_bias_enable(false)
            .depth_bias_clamp(0.0)
            .depth_bias_constant_factor(0.0)
            .depth_bias_slope_factor(0.0);

        pipeline_config.multi_sample_info
            .alpha_to_coverage_enable(false)
            .alpha_to_one_enable(false)
            .min_sample_shading(1.0)
            .sample_mask(&[])
            .rasterization_samples(vk::SampleCountFlags::TYPE_1);

        pipeline_config.color_blend_attachment
            .blend_enable(false)
            .color_write_mask(vk::ColorComponentFlags::RGBA)
            .color_blend_op(vk::BlendOp::ADD)
            .alpha_blend_op(vk::BlendOp::ADD)
            .src_color_blend_factor(vk::BlendFactor::ONE)
            .dst_color_blend_factor(vk::BlendFactor::ZERO)
            .src_alpha_blend_factor(vk::BlendFactor::ONE)
            .dst_alpha_blend_factor(vk::BlendFactor::ZERO);
        pipeline_config.color_blend_info
            .logic_op_enable(false)
            .logic_op(vk::LogicOp::COPY)
            .attachments(&[pipeline_config.color_blend_attachment])
            .blend_constants([0.0, 0.0, 0.0, 0.0]);

        pipeline_config.depth_stencil_info
            .depth_test_enable(true)
            .depth_compare_op(vk::CompareOp::LESS)
            .depth_write_enable(true)
            .depth_bounds_test_enable(false)
            .min_depth_bounds(0.0)
            .max_depth_bounds(1.0)
            .stencil_test_enable(false)
            .front(vk::StencilOpState::default())
            .back(vk::StencilOpState::default());

        pipeline_config.dynamic_state_enables = vec![vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
        pipeline_config.dynamic_state_info
            .dynamic_states(&pipeline_config.dynamic_state_enables)
            .flags(vk::PipelineDynamicStateCreateFlags::empty());

        pipeline_config.binding_descriptions = Vertex::get_binding_descriptions();
        pipeline_config.attribute_descriptions = Vertex::get_attribute_descriptions();                
    }

    pub fn enable_alpha_blending(pipeline_config: &mut PipelineConfigInfo) {
        pipeline_config.color_blend_attachment
            .blend_enable(true)
            .color_write_mask(vk::ColorComponentFlags::RGBA)
            .color_blend_op(vk::BlendOp::ADD)
            .alpha_blend_op(vk::BlendOp::ADD)
            .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
            .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
            .src_alpha_blend_factor(vk::BlendFactor::ONE)
            .dst_alpha_blend_factor(vk::BlendFactor::ZERO);
    }

    pub fn disable_depth_write(pipeline_config: &mut PipelineConfigInfo) {
        pipeline_config.depth_stencil_info.depth_write_enable(false);
    }

}

impl Drop for NjmPipeline {
    fn drop(&mut self) {
        let njm_dev = self.dev.lock().unwrap();
        if !self.vshader.is_null() {
            unsafe { njm_dev.device().destroy_shader_module(self.vshader, None) };
        }
        if !self.fshader.is_null() {
            unsafe { njm_dev.device().destroy_shader_module(self.fshader, None) };
        }
        if !self.graphics_pipeline.is_null() {
            unsafe { njm_dev.device().destroy_pipeline(self.graphics_pipeline, None) };
        }
    }
}
